﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SmartMathLibrary
{
    /// <summary>
    /// This class represents a linear spline with n linear splineparts.
    /// </summary>
    [Serializable]
    public class LinearSpline : AbstractSpline, ILinearSpline
    {
        /// <summary>
        /// The list of splineparts.
        /// </summary>
        private readonly List<LinearSplineComponent> components;

        /// <summary>
        /// Initializes a new instance of the <see cref="LinearSpline"/> class.
        /// </summary>
        public LinearSpline()
        {
            this.components = new List<LinearSplineComponent>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LinearSpline"/> class.
        /// </summary>
        /// <param name="components">The components or splineparts of the spline.</param>
        public LinearSpline(LinearSplineComponent[] components)
        {
            this.components = new List<LinearSplineComponent>();

            foreach (LinearSplineComponent c in components)
            {
                this.components.Add(c.Copy());
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LinearSpline"/> class.
        /// </summary>
        /// <param name="spline">The linear spline to clone.</param>
        public LinearSpline(LinearSpline spline)
        {
            if (spline == (LinearSpline) null)
            {
                throw new ArgumentNullException("spline");
            }

            this.components = new List<LinearSplineComponent>();

            foreach (LinearSplineComponent c in components)
            {
                this.components.Add(c.Copy());
            }
        }

        /// <summary>
        /// Gets or sets the components or parts of the spline.
        /// </summary>
        /// <value>The components or parts of the spline.</value>
        public List<LinearSplineComponent> Components
        {
            get { return components; }
        }

        /// <summary>
        /// Gets the number of splineparts.
        /// </summary>
        /// <value>The number of splineparts.</value>
        public int Count
        {
            get { return components.Count; }
        }

        /// <summary>
        /// Adds a specified splinepart to the current spline.
        /// </summary>
        /// <param name="component">The splinepart to add.</param>
        public void Add(LinearSplineComponent component)
        {
            if (component == (LinearSplineComponent) null)
            {
                throw new ArgumentNullException("component");
            }

            this.components.Add(component.Copy());
        }

        /// <summary>
        /// Removes the splinepart at the specified index.
        /// </summary>
        /// <param name="index">The specified index.</param>
        public void RemoveAt(int index)
        {
            this.components.RemoveAt(index);
        }

        /// <summary>
        /// Removes all splineparts.
        /// </summary>
        public void Clear()
        {
            this.components.Clear();
        }

        /// <summary>
        /// Calculates the value of the spline at the specified position x.
        /// </summary>
        /// <param name="x">The specified position x.</param>
        /// <returns>The s(x) of the spline.</returns>
        public override double SolveAt(double x)
        {
            foreach (LinearSplineComponent component in this.components)
            {
                if (component.IsDefinedFor(x))
                {
                    return component.LinearFunction.SolveAt(x);
                }
            }

            throw new DomainNotDefinedException("The current spline is not defined for this x value");
        }

        /// <summary>
        /// Determines whether [the current spline is defined for] [the specified x value].
        /// </summary>
        /// <param name="x">The specified x value, which should be checked.</param>
        /// <returns>
        /// 	<c>true</c> if [the current spline is defined for] [the specified x value];
        /// 	otherwise, <c>false</c>.
        /// </returns>
        public bool IsDefinedFor(double x)
        {
            foreach (LinearSplineComponent component in this.components)
            {
                if (component.IsDefinedFor(x))
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Gets the enumerator.
        /// </summary>
        /// <returns>The enumerator of the current iteration.</returns>
        public IEnumerator<LinearSplineComponent> GetEnumerator()
        {
            for (int i = 0; i < this.components.Count; i++)
            {
                yield return this.components[i];
            }
        }

        /// <summary>
        /// Creates a copy of the current instance.
        /// </summary>
        /// <returns>A copy of the current instance.</returns>
        public LinearSpline Copy()
        {
            return new LinearSpline((LinearSplineComponent[]) this.components.ToArray().Clone());
        }

        /// <summary>
        /// Compares the current instance of a linear spline to another one. 
        /// The order of the splineparts, which the spline includes doesn´t matter.
        /// </summary>
        /// <param name="compare">The cubicspline to compare.</param>
        /// <returns>True if the splines are even otherwise, false.</returns>
        public bool CompareTo(LinearSpline compare)
        {
            if (compare == (LinearSpline) null)
            {
                throw new ArgumentNullException("compare");
            }

            foreach (LinearSplineComponent c in compare)
            {
                if (!this.ComparePartTo(c))
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Checks if the current instance contains a specified LinearSplineComponent.
        /// </summary>
        /// <param name="compare">The specified LinearSplineComponent to check.</param>
        /// <returns>True if the current instance contains the specified LinearSplineComponent 
        /// otherwise, false.</returns>
        private bool ComparePartTo(LinearSplineComponent compare)
        {
            foreach (LinearSplineComponent c in this.components)
            {
                if (c.CompareTo(compare))
                {
                    return true;
                }
            }

            return false;
        }
    }
}